#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{

/*
    向文件里面写数据
    int fd = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    if(fd<0){
        perror("open");
        return 1;
    }
    const char* msg="hello bit\n";
    int cnt = 5;
    while(cnt){
        write(fd, msg, strlen(msg));
        cnt--;
    }
    close(fd);
*/

/*
    int fd = open("./log.txt", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    char buffer[1024];
    ssize_t s = read(fd, buffer, sizeof(buffer)-1);
    if(s > 0){
        buffer[s] = 0;          // 字符串结尾加上 '\0'
        printf("%s\n", buffer);
    }
    close(fd);
*/

/*
     当我们的程序运行起来后, 变成进程之后, 默认情况下, OS会默认帮助我们打开三个标准输入输出!
     0: 标准输入(键盘) 1: 标准输出(显示器) 2: 标准错误(显示器)
    
     C语言上: stdin: 标准输入(键盘), stdout: 标准输出(显示器), stderr: 标准错误(显示器)
    
     打印出的文件描述符, 会让我们想到数组的下标
     进程:文件 = 1 : n(OS打开了很多文件)
     OS需要对打开的文件进行管理 -> 先描述, 再组织
     描述: 
              struct file{
                  文件相关的属性信息
              }
     组织: 
              看图1
    
     fd: 本质是内核中进程和文件关联的数组的下标
*/

/*
    一切皆文件的理解:
        对于键盘, 显示器, 磁盘, 网卡等外设, 都是文件, 即都可以读写
        只不过对于键盘, 其写方法为空; 对于显示器, 读方法为空. 对于每个外设的读写实现逻辑在驱动层上完成
        这说明每个外设的读写方式是不一样的, 那 OS 是怎么实现一切文件的呢?
        在Linux中, 有一个 vfs(virtual file system)虚拟文件系统
        (看图2)
*/

/*
    int fd1 = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    int fd2 = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    int fd3 = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    int fd4 = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    int fd5 = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    printf("%d, %d, %d, %d, %d\n",fd1, fd2, fd3, fd4, fd5);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);
*/

/*
    向显示器写入数据
    const char* msg = "hello world!\n";
    write(1, msg, strlen(msg));
    write(2, msg, strlen(msg));
*/ 

/*
    从键盘读数据
    char buffer[64];
    ssize_t s = read(0, buffer, sizeof(buffer));
    if(s > 0){
        buffer[s] = '\0';
    }
    printf("echo# %s\n", buffer);
*/ 

/*
    文件描述符的分配规则:
        新文件分配的fd, 是从fd_array中找最小的, 没有被使用的数组位置的下标作为新文件的fd

    close(0);
    close(2)
    int fd = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    printf("fd: %d\n", fd);
*/ 

/*
    输出重定向
    理解:
        printf本质是向标准输出stdout中打印, stdout是一个类型为 extern FILE* 的文件指针,
        而 FILE 是一个C语言层面上的结构体:
        struct FILE{
            一定有一个整数, 是对应系统层面上的, 这个文件被打开时的fd.
        }
        所以 printf 是向 fd 对应的文件打印.
    close(1);
    int fd = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    printf("Hello world!\n");  
    printf("Hello world!\n");
    printf("Hello world!\n");
    printf("Hello world!\n");
*/ 

/*
    追加重定向(APPEND)
    close(1);
    int fd = open("./log.txt", O_CREAT|O_WRONLY|O_APPEND, 0644);
    printf("fd: %d\n", fd);
    printf("Hello world!\n");
    fprintf(stdout, "Hello xwl\n");
*/ 

/*
    输入重定向
        从键盘输入重定向为了从 log.txt 中输入
    close(0);
    int fd = open("./log.txt",O_RDONLY);
    printf("fd: %d\n", fd);
    char line[128];
    while(fgets(line, sizeof(line)-1,stdin)){   // stdin -> FILE* -> FILE -> fd == 0
        printf("%s", line);
    } 
*/ 
    
/*
    语言和系统的联系( FILE中有fd )
    printf("stdin -> %d\n", stdin->_fileno);
    printf("stdout -> %d\n", stdout->_fileno);
    printf("stderr -> %d\n", stderr->_fileno);
    FILE* fp = fopen("./log.txt", "r");
    if(fp == NULL){
        perror("fopen");
        return 0;
    }
    printf("fp -> %d\n", fp->_fileno);
*/ 

/*
    不关闭文件, 完成出重定向
    使用 dup2 完成重定向
        dup2(fd, 1): 将 fd 下标中的元素拷贝到 1 下标中

    int fd = open("./log.txt", O_WRONLY|O_TRUNC);//O_TRUNC :清空文件
    if(fd < 0){
        perror("open");
        return 1;
    }
    dup2(fd, 1);        // 本来应该显示到显示器上的内容, 写入到了 log.txt 文件中

    printf("my name is xwl\n");
    fprintf(stdout, "i like jog\n");
    fputs("i like eat apple\n", stdout);
*/ 

/*
    使用 dup2 完成输入重定向
    int fd = open("./log.txt", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    dup2(fd, 0);
    char buffer[1024];
    scanf("%s", buffer);        // 从 log.txt 中读取
    printf("%s\n", buffer);
*/ 

/*
    为什么所有的进程都默认打开标准输入, 标准输出, 标准错误?
        因为这些进程的父进程都是 bash, 子进程会继承父进程的属性, 即 bash 打开了这三个东西
*/
    return 0;

}
